/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package models;

import config.ConfigModels;
import config.Models.Conf_BoxEgg;
import config.Models.Conf_Cote;
import db.Redis_Rd;
import db.Redis_W;
import entities.*;
import entities.Box_Egg.BoxEggDB;
import entities.Cote.CoteDB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import libCore.*;
import org.json.simple.JSONValue;
import share.KeysDefinition;
import share.ShareConstants;
import share.ShareMacros;
import utilities.time.UtilTime;

/**
 *
 * @author LinhTA
 */
public class U_CoteModel implements BaseModel {

    public Map<String, Cote> cotes;
    
    public String defaultKeyCote;
    public Cote uDefaultCote;
    public int sumCote;
    
    public String uId;

    public U_CoteModel( String uid ) 
    {
        uId         =   uid;
        cotes       =   new HashMap<String, Cote>();
        uDefaultCote=   new Cote();
        sumCote     =   0;
    }

    @Override
    public void setDB() 
    {
    }

    @Override
    public void setData(Map<String, String> obj) 
    {
    }

    @Override
    public void getDB(String uid) 
    {
        String keyCotes = KeysDefinition.getKey_ListCotes(uId);

        Set<String> slist   =   Redis_Rd.getInstance().smember(keyCotes);
        sumCote = slist.size();
        
        int nDefaultCote     =   Integer.parseInt(Config.getParam( "constants", "DefaultCote" ));
        
        if( nDefaultCote <= slist.size() )
        {
            CoteDB coteDB   =   new CoteDB();
            defaultKeyCote  =   keyCotes + "_" + nDefaultCote;
            uDefaultCote    =   getCote( defaultKeyCote, coteDB );

            updateCoteAndDB( defaultKeyCote, uDefaultCote, coteDB );
        }
        
        for( int i = 0; i < sumCote; ++i ) 
        {    
            String kCote    =   slist.toArray()[i].toString();
            String order    =   kCote.substring(kCote.length()-1, kCote.length());
            Cote c          =   new Cote();
            c.order         =   Integer.parseInt( order );
            // Another cote will be fake cote, just care about coteId and order
            // Client will load real cote when they change cote
            cotes.put(kCote, c);
        }
        cotes   =   sortCotesOrder(cotes);
    }

    public Map<String, Object> toAMF() 
    {
        Map<String, Object> data = new HashMap<String, Object>();

        List<String> keys       =   new ArrayList<String>( cotes.keySet() );
        
        // Add default cote
        Map<String, Object> dataDefaultCote =   uDefaultCote.toAMF();
        dataDefaultCote.put( share.ShareMacros.COTES_ID, defaultKeyCote );
        data.put( share.ShareMacros.COTES_DEFAULT, dataDefaultCote );
        
        // Add cote counter
        data.put(share.ShareMacros.COTES_SUM, sumCote);
        
        // Add list cote
        Map<String, String> mKeys   =   new HashMap<String, String>();
        for( int i = 0; i < keys.size(); ++i ) {
            mKeys.put( String.valueOf( i ), keys.get(i) );
        }
        data.put(share.ShareMacros.COTES_LIST, Util.obj2Map(mKeys) );
        
        return data;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////// 
    // Static function
    
    /*
    Create user cotes profile
    */
    public static U_CoteModel isNewUser(String uid) 
    {
        U_CoteModel coteModel   =   new U_CoteModel(uid);

        int nCote   =   Integer.parseInt(Config.getParam("defaultUser", "nDefaultCote"));
        int defaultCoteNumber   =   Integer.parseInt(Config.getParam("constants", "DefaultCote"));
        String[] keyCotes       =   new String[nCote];
        for( int i = 0; i < nCote; ++i )
        {
            String keyPegs  =   KeysDefinition.randomKeySTR();
            String keyEggs  =   KeysDefinition.randomKeySTR();
            String keyBoxEgg=   KeysDefinition.randomKeySTR();

            String keyCote  =   KeysDefinition.getKey_CoteOrder( KeysDefinition.getKey_ListCotes(uid), String.valueOf( i ));
            
            Cote c = defaultCote(uid, keyPegs, keyBoxEgg, keyEggs);

            CoteDB cotedb   =   new CoteDB();
            cotedb.level    =   String.valueOf(c.level);
            cotedb.pegs     =   keyPegs;
            cotedb.eggs     =   keyEggs;
            cotedb.boxegg   =   keyBoxEgg;
            cotedb.fish     =   String.valueOf(c.food);
            cotedb.order    =   String.valueOf( i );

            if ( coteModel.setCoteDB(keyCote, cotedb) ) {
                coteModel.cotes.put(keyCote, c);
                keyCotes[i] =   keyCote;
            }
            if( i == defaultCoteNumber )
            {
                coteModel.uDefaultCote  =   c;
                coteModel.defaultKeyCote=   keyCote;
            }
        }
        
        coteModel.cotes   =   sortCotesOrder(coteModel.cotes);
        
        if ( Redis_W.getInstance().sadd( KeysDefinition.getKey_ListCotes(uid), keyCotes ) != nCote ) 
        {
            return null;
        }

        coteModel.sumCote      =    nCote;
        
        return coteModel;
    }

    public static Cote defaultCote( String uid, String keyPegs, String keyBoxEgg, String keyEggs ) 
    {
        Cote co     =   new Cote();
        long time   =   UtilTime.getTimeNow();
        co.pegs     =   U_PenguinModel.isNewUser(uid, keyPegs, time);
        co.boxegg   =   U_BoxEggModel.isNewUser(uid, keyBoxEgg, time);
        co.eggs     =   U_EggModel.isNewUser(uid, keyEggs, time);
        co.level    =   1;
        co.food     =   0;

        return co;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////// 
    
    public Cote getCote(String idCote, CoteDB coteDB) 
    {
        Cote cote = new Cote();

        String coteStr = Redis_Rd.getInstance().get(idCote);
        Map<String, String> data = (HashMap<String, String>) JSONValue.parse(coteStr);
        coteDB.setData(data);

        cote.food   =   Integer.parseInt(coteDB.fish);
        U_PenguinModel pengModel    =   new U_PenguinModel( uId );
        cote.pegs   =   pengModel.getDBData( coteDB.pegs );
        
        U_BoxEggModel boxModel      =   new U_BoxEggModel( uId );
        cote.boxegg =   boxModel.getDBData( coteDB.boxegg );
        cote.level  =   Integer.parseInt(coteDB.level);

        Map<String, Egg> listEggs   =   U_EggModel.getDBEggs(coteDB.eggs);
        cote.eggs = U_EggModel.parseTimeEggs(listEggs);

        return cote;
    }
    
    //================ Hieu - update ==========================
    
    public Cote updateCote(Cote cote, CoteDB coteDB) 
    {
        Map<Long, Map<String, Penguin>> hungryPeg = new HashMap<Long, Map<String, Penguin>>();
        Map<Long, Map<String, Egg>> timeEgg = new HashMap<Long, Map<String, Egg>>();

        Long timeNow = UtilTime.getTimeNow();
        
        // Feed penguin and collect egg
        U_PenguinModel penModel =   new U_PenguinModel( uId );
        
        penModel.getPegEggs(timeEgg, cote, hungryPeg, timeNow);
        while (penModel.pegEatAuto(hungryPeg, cote) == true) {
            penModel.getPegEggs(timeEgg, cote, hungryPeg, timeNow);
        }
        System.out.println( "----> Update Cote: End get egg left " + cote.food + " food" );

        Box_Egg box = cote.boxegg;
        Map<String, Egg> eggsBox = box.eggs;
        
        // List new egg in box 
        Map<String, Set<String>> newEggsInbox    =   new HashMap<String, Set<String>>();
        //Set<String> newEggsInbox    =   new HashSet<String>();
        // List new egg in cote
        Map<String, Set<String>> newEggsCote        =   new HashMap<String, Set<String>>();
        //Set<String> newEggsCote     =   new HashSet<String>();
        
        devideEggs(eggsBox, box, timeEgg, newEggsInbox);
        
        cote.boxegg = box;
        cote.eggs.putAll(timeEgg);
        
        // Update eggs cote
        addEggsDB(timeEgg, newEggsCote, coteDB);
        
        // Update box eggs cote
        addBoxEggsDB(newEggsInbox, coteDB);
        
        // Update coteDB for use after
        coteDB.fish     =   String.valueOf(cote.food);
        
        return cote;
    }
    
    protected void devideEggs( Map<String, Egg> eggsBox, Box_Egg box, 
            Map<Long, Map<String, Egg>> timeEgg, Map<String, Set<String>> newEggsInbox ) 
    {
        
        // Current box egg limitation
        Conf_BoxEgg confBox = ConfigModels.conf_boxEggs.get( String.valueOf( box.level ) );
        
        if (eggsBox.size() < confBox.maxEgg) 
        {
            List<Long> listTimeEgg = new ArrayList<Long>();
            for (Map.Entry<Long, Map<String, Egg>> entry : timeEgg.entrySet()) {
                long eggId = entry.getKey();
                listTimeEgg.add(eggId);
            }
            // Sort egg time eat ASC
            Collections.sort(listTimeEgg);

            for (int i = 0; i < listTimeEgg.size(); i++) 
            {
                // Out of box egg limited
                if (eggsBox.size() == confBox.maxEgg) {
                    break;
                }
                Map<String, Egg> eggAdd = timeEgg.get(listTimeEgg.get(i));
                /* Insert to box egg ( with collection )
                 * that means insert all egg at that time to box egg
                 */
                if (eggAdd.size() + eggsBox.size() <= confBox.maxEgg) 
                {
                    // add new egg to list
                    for( Map.Entry<String, Egg> entry : eggAdd.entrySet() )
                    {
                        // if not contain this type in collection
                        String eggType  =   entry.getValue().kind;
                        if( !newEggsInbox.containsKey( eggType ) )
                        {
                            Set<String> eggTypeCollection   =   new HashSet<String>();
                            newEggsInbox.put( eggType, eggTypeCollection );
                        }
                        newEggsInbox.get( eggType ).add( entry.getKey() );
                    }
                    
                    eggsBox.putAll(eggAdd);
                    timeEgg.remove(listTimeEgg.get(i));
                } 
                else // Insert to box egg
                {
                    // Try to get eggs at time from collection
                    Map<String, Egg> eggTime    =   timeEgg.get(listTimeEgg.get(i));

                    Map<String, Egg> mEggAddClone   =   new HashMap<String, Egg>( eggAdd );
                    for (Map.Entry<String, Egg> entry : mEggAddClone.entrySet()) 
                    {
                        String keyEgg   =   entry.getKey();
                        Egg egg         =   entry.getValue();

                        // add new egg to collection
                        if( !newEggsInbox.containsKey( egg.kind ) )
                        {
                            Set<String> eggTypeCollection   =   new HashSet<String>();
                            newEggsInbox.put( egg.kind, eggTypeCollection );
                        }
                        newEggsInbox.get( egg.kind ).add( entry.getKey() );

                        eggTime.remove( keyEgg );
                        eggsBox.put( keyEgg, egg );

                        if (eggsBox.size() >= confBox.maxEgg) {
                            timeEgg.put(listTimeEgg.get(i), eggTime);
                            break;
                        }
                    }
                }
            }
            box.eggs = eggsBox;
        }
    }
    
    protected void addEggsDB( Map<Long, Map<String, Egg>> timeEgg, Map<String, Set<String>> newEggsCote, CoteDB coteDB ) 
    {
        for (Map.Entry<Long, Map<String, Egg>> entry : timeEgg.entrySet()) {
            Map<String, Egg> eggTimeInfo    =   entry.getValue();
            for( Map.Entry<String, Egg> eggEntry : eggTimeInfo.entrySet() ) 
            {
                String eggType  =   eggEntry.getValue().kind;
                if( !newEggsCote.containsKey( eggType ) )
                {
                    Set<String> eggTypeCollection   =   new HashSet<String>();
                    newEggsCote.put( eggType, eggTypeCollection );
                }
                newEggsCote.get( eggType ).add( eggEntry.getKey() );
            }
        }
        U_EggModel.addListEggs( coteDB.eggs, newEggsCote );
    }
    
    protected void addBoxEggsDB( Map<String, Set<String>> newEggsInbox, CoteDB coteDB ) 
    {
        if( newEggsInbox.size() > 0 )
        {
            String keyBoxEgg    =   coteDB.boxegg;
            
            U_BoxEggModel boxModel  =   new U_BoxEggModel( uId );
            BoxEggDB boxEggDB   =   boxModel.getBoxEggDB( keyBoxEgg );
            U_EggModel.addListEggs( boxEggDB.eggs, newEggsInbox );
        }
    }
    
    //================ Hieu - end update ==========================
    
    public Cote updateCoteAndDB( String coteId, Cote cote, CoteDB coteDB ) 
    {
        int lastFood        =   cote.food;
        updateCote( cote, coteDB );
        int foodConsume =   lastFood - cote.food;
        // Update user exp
        UserModel u     =   new UserModel( uId );
        
        u.increaseUserExp( foodConsume );
        setCoteDB(coteId, coteDB);
        
        return cote;
    }
    
    //================ Hieu ==========================
    
    public Map<String, Object> updatePenguin( Map<String, Object> params ) 
    {
        Map<String, Object> result  =   new HashMap<String, Object>();
        
        String pengId       =   params.get( ShareConstants.PENG_ID ).toString();
        String coteId       =   params.get( ShareConstants.COTE_ID ).toString();
        
        String peg = Redis_Rd.getInstance().get( pengId );
        Map<String, Object> data    =   (HashMap<String, Object>) JSONValue.parse(peg);
        Penguin peng = new Penguin();
        peng.setData(data);
        
        CoteDB coteDB   =   new CoteDB();
        Cote cote       =   getCote( coteId, coteDB );
        
        ///////////////////////////////////////////
        
        Map<Long, Map<String, Penguin>> hungryPeg = new HashMap<Long, Map<String, Penguin>>();
        Map<Long, Map<String, Egg>> timeEgg = new HashMap<Long, Map<String, Egg>>();

        Long timeNow = UtilTime.getTimeNow();
        
        // Feed penguin and collect egg
        U_PenguinModel penModel =   new U_PenguinModel( uId );
        penModel.updatePenguin(pengId, peng, timeEgg, cote, hungryPeg, timeNow);
        penModel.pegEatAuto(hungryPeg, cote);
        
        System.out.println( "----> End get egg penguin left " + cote.food + " food" );

        Box_Egg box = cote.boxegg;
        Map<String, Egg> eggsBox = box.eggs;
        
        // List new egg in box
        Map<String, Set<String>> newEggsInbox    =   new HashMap<String, Set<String>>();
        // List new egg in cote
        Map<String, Set<String>> newEggsCote     =   new HashMap<String, Set<String>>();
        
        devideEggs(eggsBox, box, timeEgg, newEggsInbox);
        
        cote.boxegg = box;
        cote.eggs.putAll(timeEgg);
        
        // Update eggs cote
        addEggsDB(timeEgg, newEggsCote, coteDB);
        
        // Update box eggs cote
        addBoxEggsDB(newEggsInbox, coteDB);
        
        // Update coteDB for use after
        coteDB.fish     =   String.valueOf(cote.food);
        
        setCoteDB(coteId, coteDB);
        ////////////////////////////////////////////
        // Get the first egg ( box egg has high priority )
        String eggId    =   "";
        
        Iterator iteBox, iteCote;
        
        if( newEggsInbox.size() > 0 )
        {
            iteBox  =   ((Map.Entry<String, Set<String>>)(newEggsInbox.entrySet().iterator()).next()).getValue().iterator();
            if( iteBox.hasNext() )
                eggId       =   (String) iteBox.next();
        }
        if( newEggsCote.size() > 0 )
        {
            iteCote =   ((Map.Entry<String, Set<String>>)(newEggsCote.entrySet().iterator()).next()).getValue().iterator();
            if( iteCote.hasNext() )
                eggId       =   (String) iteCote.next();
        }
        
        Egg  egg        =   null;
        if( eggId.equals("") )
        {
            // Empty egg on the sand --> remove the last egg by time in box egg
            if( cote.eggs.size() <= 0 )
            {
                List<Egg> listEggBoxEgg =   new ArrayList<Egg>(cote.boxegg.eggs.values());
                egg     =   U_EggModel.lastestEgg((ArrayList)listEggBoxEgg);
                for( Map.Entry<String, Egg> entry : cote.boxegg.eggs.entrySet() )
                {
                    if( entry.getValue() == egg )
                    {
                        eggId   =   entry.getKey();
                        break;
                    }
                }
            }
            // Has egg on the sand --> remove the last egg by time in sand
            else 
            {
                Long lastestTime    =   0L;
                for( Map.Entry<Long, Map<String, Egg>> etime : cote.eggs.entrySet() )
                {
                    Long eggT       =   etime.getKey();
                    lastestTime     =   ( lastestTime > 0 ) ? ( lastestTime < eggT ? eggT : lastestTime ) : eggT;
                }
                // found
                if( lastestTime != 0L )
                {
                    Map<String, Egg> listEggSand    =   cote.eggs.get( lastestTime );
                    for( Map.Entry<String, Egg> etime : listEggSand.entrySet() )
                    {
                        eggId   =   etime.getKey();
                        egg     =   etime.getValue();
                        break;
                    }
                }
            }
        }
        else
            egg     =   U_EggModel.getDBEgg( eggId );
        
        if( egg != null )
        {
            result.put( ShareConstants.EGG_ID, eggId );
            result.put( ShareConstants.EGG, egg.toAMF() );
        }
        
        return result;
    }
    
    public Boolean setCoteDB( String keyCote, CoteDB coteDB ) 
    {
        return Redis_W.getInstance().set(keyCote, Util.obj2String(coteDB)) != -1;
    }
    
    public Cote getUserCote( String idCote ) 
    { 
        
        CoteDB  coteDB      =   new CoteDB();
        Cote cote           =   getCote( idCote, coteDB );
        //WARNING U_CoteModel.updateCote( cote, coteDB );
        cote    =   updateCoteAndDB( idCote, cote, coteDB );
        
        return cote;
    }
    
    public Boolean dropFish( String coteId, int nFish ) 
    {
        Boolean result      =   false;
        UserModel uModel    =   new UserModel( uId );
        User um             =   uModel.getDBUser();
        
        if( um.fish >= nFish )
        {
            CoteDB  coteDB      =   new CoteDB();
            Cote cote           =   getCote( coteId, coteDB );
            updateCoteAndDB( coteId, cote, coteDB );
            
            cote.food           +=  nFish;
            um.fish             -=  nFish;
            // Increase user gold
            um.exp              +=  nFish;
            
            // Update coteDB for use after
            coteDB.fish     =   String.valueOf(cote.food);
            setCoteDB(coteId, coteDB);
            
            // Update user info
            uModel.updateDBUser( um );
            
            result          =   true;
        }
        
        return result;
    }
    
    public Map<String, Object> feedPenguin( String coteId, String pengId ) 
    {
        Map<String, Object> result  =   new HashMap<String, Object>();
        
        CoteDB  coteDB      =   new CoteDB();
        Cote cote           =   getCote( coteId, coteDB );
        updateCoteAndDB( coteId, cote, coteDB );
        
        String pengStr      =   Redis_Rd.getInstance().get( pengId );
        Map<String, String> mPengData   =   (Map<String, String>) JSONValue.parse( pengStr );
        Map<String, Object> data        =   Util.obj2Map(mPengData);
        data.put( ShareConstants.PENG_ID, pengId );
        
        result.put( "Penguin", data );
        
        return result;
    }
    
    public Map<String, Object> feedPenguins( String coteId, ArrayList<String> pengIds ) 
    {
        Map<String, Object>    results     =   new HashMap<String, Object>();
        
        for( int i = 0; i < pengIds.size(); ++i ) 
        {
            String id       =   pengIds.get(i);
            Map<String, Object> result  =   feedPenguin( coteId, id );
            results.put( id, result );
        }
        
        return results;
    } 
    
    public int sellEggRandomInSand( String coteId, String eggType ) 
    {
        int gold    =   -1;
        
        // Still not has egg
        CoteDB coteDB   =   new CoteDB();
        Cote cote       =   getCote( coteId, coteDB );
        updateCoteAndDB( coteId, cote, coteDB );

        Map<String, Egg> eggs1   =   U_EggModel.getDBEggs( coteDB.eggs );
        if( eggs1.size() <= 0 )
            System.out.println( "-----> Empty list !" );

        String kEgg     =   Redis_W.getInstance().spop( coteDB.eggs + eggType );
        if( kEgg != null )
        {
            Egg egg     =   U_EggModel.getDBEgg(kEgg);
            gold        =   egg.gold;
            UserModel uModel    =   new UserModel( uId );
            uModel.increaseUserGold( egg.gold );
            U_EggModel.delEggDB(kEgg);
        }
        return gold;
    }
    
    public int stealEgg( String coteId, String eggId ) 
    {
        CoteDB  coteDB      =   new CoteDB();
        getCote( coteId, coteDB );
        
        String eggStr       =   Redis_Rd.getInstance().get(eggId);
        Map<String, Object> dataEgg =   new HashMap<String, Object>();
        dataEgg.putAll((HashMap<String, String>) JSONValue.parse(eggStr));
        
        // remove from collection
        boolean result      =   U_EggModel.removeEggDB( coteDB.eggs, String.valueOf(dataEgg.get( ShareMacros.KIND )), eggId );
        int eggGold         =   0;
        if( dataEgg.size() > 0 && result )
        {
            // Delete from db
            U_EggModel.delEggDB(eggId);
            
            Egg egg         =   new Egg();
            egg.setData( dataEgg );
            // Update user info gold
            eggGold     =   egg.gold;
        }
        
        return eggGold;
    }
    
    public Map<String, Object> sellEggsBox( String coteId, String[] eggIds ) 
    {
        Map<String, Object> mData   =   new HashMap<String, Object>();
        
        Boolean result;
        
        String coteStr = Redis_Rd.getInstance().get(coteId);
        Map<String, String> data    =   (HashMap<String, String>) JSONValue.parse(coteStr);
        CoteDB coteDB   =   new CoteDB();
        coteDB.setData(data);
        
        String boxEggStr = Redis_Rd.getInstance().get( coteDB.boxegg );
        Map<String, String> dataBoxEgg  =   (HashMap<String, String>) JSONValue.parse(boxEggStr);
        BoxEggDB boxEggDB   =   new BoxEggDB();
        boxEggDB.setData(dataBoxEgg);
        
        int eggGold         =   0;
        int numberEggRemove =   0;
        for( String type : eggIds )
        {
            // Get random egg with type from collection
            // Remove it from redis
            // Remove it from collection
            String randomEgg=   Redis_W.getInstance().spop( boxEggDB.eggs + type );
            if( randomEgg != null )
            {
                Egg e   =   U_EggModel.getDBEgg( randomEgg );
                eggGold +=  e.gold;
                U_EggModel.delEggDB(randomEgg);
                ++numberEggRemove;
            }
        }
        
        // Move egg from sand to box egg
        // Get all egg
        Map<String, Egg> mEggsSand   =   U_EggModel.getDBEggs( coteDB.eggs );
        // Sort by time birth
        // sortByComparator( mEggsSand );

        // move to box
        int counter =   0;
        for( Map.Entry<String, Egg> mEntry : mEggsSand.entrySet() )
        {
            if( counter >= numberEggRemove || counter >= mEggsSand.size() )
                break;

            String id   =   mEntry.getKey();
            mData.put( ShareConstants.EGG_ID + counter, id );
            Redis_W.getInstance().smove( coteDB.eggs, boxEggDB.eggs, id );
            counter++;
        }
        mData.put( ShareConstants.NUMBER_EGG, counter );

        // Update user info gold
        UserModel uModel    =   new UserModel( uId );
        uModel.increaseUserGold( eggGold );
        
        result      =   eggGold != -1;
        System.out.println( "-----> Result: " + ( result ? "Success" : "Failed") );
        
        mData.put( ShareConstants.CAN_SELL, numberEggRemove == eggIds.length );
        mData.put( ShareConstants.EGG_PRICE, eggGold );
        
        return mData;
    }
    
    public Boolean addPenguin( String coteId, String pengId, String pengStr ) 
    {
        CoteDB  coteDB      =   new CoteDB();
        getCote( coteId, coteDB );
        
        return U_PenguinModel.addPenguinDB(coteDB.pegs, pengId, pengStr );
    }
    
    public Boolean deletePenguin( String coteId, String pengId ) 
    {
        CoteDB  coteDB      =   new CoteDB();
        getCote( coteId, coteDB );
        
        return U_PenguinModel.removePenguinDB(coteDB.pegs, pengId );
    }
    
    public Boolean deletePenguins( String coteId, String[] pengIds ) 
    {
        CoteDB  coteDB      =   new CoteDB();
        getCote( coteId, coteDB );
        
        return U_PenguinModel.removePenguinDB( coteDB.pegs, pengIds );
    }
    
    public Boolean upgradeCoteByGold( String coteId ) 
    {
        Boolean result  =   false;
        UserModel uModel    =   new UserModel( uId );
        
        if( uModel.isValidCote( coteId ) ) {
            
            CoteDB  coteDB      =   new CoteDB();
            Cote cote           =   getCote( coteId, coteDB );
            //WARNING U_CoteModel.updateCote( cote, coteDB );
            updateCoteAndDB( coteId, cote, coteDB );
            
            // Get current user information
            User user       =   uModel.getDBUser();
            
            // Get next config cote
            Map<String, Conf_Cote> mconfCote    =   config.ConfigModels.conf_cotes;
            Conf_Cote confCote                  =   mconfCote.get( String.valueOf( cote.level + 1 ) );
            
            if( confCote.require.gold <= user.gold && confCote.require.level <= user.level ) {
                
                // Increase box egg level
                cote.level        +=  1;
                Map<String, Object> data    =   (HashMap<String, Object>)JSONValue.parse(Redis_Rd.getInstance().get( coteId ));
                data.put( "level", cote.level );
                String newCoteStr=  Util.obj2String(data);
                Redis_W.getInstance().set( coteId, newCoteStr );
                
                // Update user gold
                return uModel.updateUserGold( user.gold - confCote.require.gold );
            }
        }
        return result;
    }
    
    public Boolean upgradeCoteByCoin( String coteId ) 
    {
        Boolean result  =   false;
        UserModel uModel    =   new UserModel( uId );
        
        if( uModel.isValidCote( coteId ) ) 
        {
            CoteDB  coteDB      =   new CoteDB();
            Cote cote           =   getCote( coteId, coteDB );
            //WARNING U_CoteModel.updateCote( cote, coteDB );
            updateCoteAndDB( coteId, cote, coteDB );
            
            // Get current user information
            User user       =   uModel.getDBUser();
            
            // Get next config cote
            Map<String, Conf_Cote> mconfCote    =   config.ConfigModels.conf_cotes;
            Conf_Cote confCote                  =   mconfCote.get( String.valueOf( cote.level + 1 ) );
            
            if( confCote.require.coin <= user.coin && confCote.require.level <= user.level ) {
                
                // Increase box egg level
                cote.level        +=  1;
                Map<String, Object> data    =   (HashMap<String, Object>)JSONValue.parse(Redis_Rd.getInstance().get( coteId ));
                data.put( "level", cote.level );
                String newCoteStr=  Util.obj2String(data);
                Redis_W.getInstance().set( coteId, newCoteStr );
                
                // Update user gold
                return uModel.updateUserCoin( user.coin - confCote.require.coin );
            }
        }
        return result;
    }
    //================ Hieu ==========================
    private static Map sortCotesOrder( Map unsortMap ) {
        
        List list = new LinkedList(unsortMap.entrySet());

        // sort list based on comparator
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2)
            {
                Cote cote1 = (Cote)((Comparable) ((Map.Entry) (o1)).getValue());
                Cote cote2 = (Cote)((Comparable) ((Map.Entry) (o2)).getValue());
                if( cote1.order < cote2.order )
                    return -1;
                else if( cote1.order > cote2.order )
                    return 1;
                else
                    return 0;
            }
        });

        // put sorted list into map again
        //LinkedHashMap make sure order in which keys were inserted
        Map sortedMap = new LinkedHashMap();
        for (Iterator it = list.iterator(); it.hasNext();) {
                Map.Entry entry = (Map.Entry) it.next();
                sortedMap.put(entry.getKey(), entry.getValue());
        }
        return sortedMap;
    }
    
    private static Map sortByComparator(Map unsortMap) {
 
        List list = new LinkedList(unsortMap.entrySet());

        // sort list based on comparator
        Collections.sort(list, new Comparator() {
                @Override
                public int compare(Object o1, Object o2) 
                {
                    if( ((Egg)o1).timeBirth < ((Egg)o2).timeBirth )
                        return -1;
                    else if( ((Egg)o1).timeBirth > ((Egg)o2).timeBirth )
                        return 1;
                    else 
                        return 0;
                }
        });

        // put sorted list into map again
        //LinkedHashMap make sure order in which keys were inserted
        Map sortedMap = new LinkedHashMap();
        for (Iterator it = list.iterator(); it.hasNext();) {
                Map.Entry entry = (Map.Entry) it.next();
                sortedMap.put(entry.getKey(), entry.getValue());
        }
        return sortedMap;
    }

    //=================================================
    
}